/*******************************************************************/
/*    Copyright (c) 1999-2020 by Spatial Corp.                     */
/*    All rights reserved.                                         */
/*    Protected by U.S. Patents 5,257,205; 5,351,196; 6,369,815;   */
/*                              5,982,378; 6,462,738; 6,941,251    */
/*    Protected by European Patents 0503642; 69220263.3            */
/*    Protected by Hong Kong Patent 1008101A                       */
/*******************************************************************/
//----------------------------- IopAcisConfigurationImport.cpp -------------------------------

#include <stdio.h>

// ACIS
#include "lists.hxx"
#include "kernapi.hxx" 

// ACIS-IOP
#include "SPAIop.h"
#include "SPAIopAcis.h"
#include "SPAIopArgParser.h"
#include "SPAIopPolicyUtilities.h"
#include "SPAIopPrintUtilities.h"
#include "SPAIopAcisSampleUtilities.h"
#include "SPAIopSampleResultUtils.h"
#include "SPAIopPerformanceStatistics.h"

//-----------------------------------------------------------------------------------------

// License file
#include "spatial_license.h"
#include "spa_unlock_result.hxx"

void PrintHelp();
void MyProgressCallBack(double iProgress, bool& iAbort);
bool PartConfigurationAlreadyImported(SPAIopInputPSReference& iReference, const SPAIopWString& iConfigName);
bool CacheImportedPartConfiguration(SPAIopInputPSReference& iReference, const SPAIopWString& iConfigName, const ENTITY_LIST& iAcisEntities);
void ClearImportedPartConfigurationCache(void);
int ImportConfiguration(const wchar_t*& ipSourceFilePath, const wchar_t*& ipConfigurationName, SPAIopPolicy& ioPolicy);
int ProcessInputProductStructure(SPAIopInputProductStructure& iInputPS, SPAIopPSImportResult &iInputPSResult, const SPAIopPolicy& iPolicy, const SPAIopFormatType& iOptionFormat);
int ProcessPSReference(SPAIopInputPSReference& iInputReference, const SPAIopWString& iInstanceConfigName, const SPAIopPolicy& iPolicy, const SPAIopFormatType& iOptionFormat);
bool ImportProductStructure(SPAIopDocument& ioSourceDocument, const SPAIopPolicy& iPolicy, SPAIopInputProductStructure& oInputPS, SPAIopPSImportResult &oInputPSResult);
int ImportPart(SPAIopInputPSReference& iInputReference, const SPAIopPolicy& iPolicy, ENTITY_LIST& oAcisEntities);

//-----------------------------------------------------------------------------------------

class ProgressReporter;
ProgressReporter* _g_pProgressReporter = NULL;

const unsigned short CGM_ATTR_COLOR_UNDEFINED = 256;

typedef std::pair< unsigned int, std::wstring > UniqueReferenceConfigurationID;
typedef std::pair< UniqueReferenceConfigurationID, ENTITY_LIST > PartConfiguration;

typedef std::map< UniqueReferenceConfigurationID, ENTITY_LIST > ImportedPartConfigurationsMap;
typedef std::map< UniqueReferenceConfigurationID, ENTITY_LIST >::iterator ImportedPartConfigurationsMapIter;

ImportedPartConfigurationsMap g_ImportedPartConfigurationsCache;

//-----------------------------------------------------------------------------------------

using namespace SPAIopAcisPrintUtils;
using namespace SPAIopPolicyUtilities;
using namespace SPAIopPrintUtilities;

//-----------------------------------------------------------------------------------------

void PrintHelp()
{
	PrintMessage(L"Usage: IopAcisConfigurationImport -i <full-input-file-path> [-o <full-output-dump-file-path>] [-p <full-policy-file-path>] [-log <full-log-file-path>] [-config <name-of-configuration-to-import>] [-h] \n");
	PrintMessage(L"\t-i		: The absolute path to the input file. \n");
	PrintMessage(L"\t-o		: The absolute path to the output dump file (.txt). \n");
	PrintMessage(L"\t-p		: The absolute path to the policy file used to control optional behavior during operations. \n");
	PrintMessage(L"\t-log	: The absolute path to the log file. \n");
	PrintMessage(L"\t-config	: The name of the configuration to import. If not specified, the default configuration will be imported. \n");
	PrintMessage(L"\t-h		: This help.\n");
}

//-----------------------------------------------------------------------------------------

class ProgressReporter
{
public:

	//-----------------------------------------------------------------------------------------

	ProgressReporter() : _psDone(false), _numParts(0), _partsDone(0), _lastProgress(-1) {}

	//-----------------------------------------------------------------------------------------

	void ProgressCallBack(double iProgress, bool& iAbort)
	{
		int iProgressRange = 100;
		int percent = 0;

		double actualProgressPercent = iProgress * 100;
		int progress = static_cast<int>(actualProgressPercent);

		if (!_psDone)
		{
			percent = (int)((100.0 * progress) / double(iProgressRange));
			PrintMessage(L"Product Structure Progress : %d%%\n", percent);
		}
		else
		{
			size_t cnt = _partsDone * iProgressRange + progress;
			percent = (int)((100.0 * cnt) / (_numParts * iProgressRange));
			if (percent > _lastProgress)
			{
				PrintMessage(L"Translation Progress : %d%%\n", percent);
			}

			_lastProgress = percent;
			if (progress == iProgressRange)
			{
				_partsDone += 1;
			}
		}
	}

	//-----------------------------------------------------------------------------------------

	void SetNumParts(int iNumParts)
	{
		_numParts = iNumParts;
	}

	//-----------------------------------------------------------------------------------------

	void SetPsDone(bool isDone)
	{
		_psDone = isDone;
	}

	//-----------------------------------------------------------------------------------------

private:

	//-----------------------------------------------------------------------------------------

	bool _psDone;
	size_t _numParts;
	size_t _partsDone;
	int _lastProgress;

	//-----------------------------------------------------------------------------------------
};

//-----------------------------------------------------------------------------------------

void MyProgressCallBack(double iProgress, bool& iAbort)
{
	if (_g_pProgressReporter != NULL)
	{
		_g_pProgressReporter->ProgressCallBack(iProgress, iAbort);
	}
}

//-----------------------------------------------------------------------------------------

bool PartConfigurationAlreadyImported(SPAIopInputPSReference& iReference, const SPAIopWString& iConfigName)
{
	bool alreadyImported(false);

	unsigned int currentRefID = iReference.GetUniqueID();
	const wchar_t* pConfigName = NULL;
	iConfigName.ToWChar(pConfigName);
	std::wstring currentConfigName(pConfigName);
	UniqueReferenceConfigurationID searchUniqueConfigID(currentRefID, currentConfigName);

	ImportedPartConfigurationsMapIter foundUniqueConfigID = g_ImportedPartConfigurationsCache.find(searchUniqueConfigID);
	if (foundUniqueConfigID != g_ImportedPartConfigurationsCache.end())
	{
		alreadyImported = true;
	}

	return alreadyImported;
}

//-----------------------------------------------------------------------------------------

bool CacheImportedPartConfiguration(SPAIopInputPSReference& iReference, const SPAIopWString& iConfigName, const ENTITY_LIST& iAcisEntities)
{
	bool recordedImportedPart = false;

	bool alreadyImported = PartConfigurationAlreadyImported(iReference, iConfigName);
	if (false == alreadyImported && 0 < iAcisEntities.count())
	{
		unsigned int uniqueRefID = iReference.GetUniqueID();
		const wchar_t* pConfigName = NULL;
		iConfigName.ToWChar(pConfigName);
		std::wstring configName(pConfigName);
		UniqueReferenceConfigurationID importedRefConfigID(uniqueRefID, configName);
		PartConfiguration importedPartConfig(importedRefConfigID, iAcisEntities);

		g_ImportedPartConfigurationsCache.insert(importedPartConfig);

		recordedImportedPart = true;
	}

	return recordedImportedPart;
}

//-----------------------------------------------------------------------------------------

void ClearImportedPartConfigurationCache(void)
{
	ImportedPartConfigurationsMapIter importedPartConfigIter = g_ImportedPartConfigurationsCache.begin();
	while (importedPartConfigIter != g_ImportedPartConfigurationsCache.end())
	{
		ENTITY_LIST acisEntities = importedPartConfigIter->second;
		acisEntities.clear();

		importedPartConfigIter++;
	}

	g_ImportedPartConfigurationsCache.clear();
}

//-----------------------------------------------------------------------------------------

int ImportConfiguration(const wchar_t*& ipSourceFilePath, const wchar_t*& ipConfigurationName, SPAIopPolicy& ioPolicy)
{
	int returnValue = SPAIopSampleResult_Import_Ok;

	_g_pProgressReporter = new ProgressReporter();

	// Create Source Document from input file
	std::wstring sourceFilePath(ipSourceFilePath);
	SPAIopWString wSourceFilePath(sourceFilePath.c_str());
	SPAIopDocument sourceDoc(wSourceFilePath);

	SPAIopFormatType documentFormatType = SPAIopFormatType_Unknown;
	SPAIopDocumentInformation sourceDocInfo = sourceDoc.GetInformation();
	SPAIopWString documentFormatString;
	bool gotDocumentFormat = sourceDocInfo.GetFormatType(documentFormatString);
	if (true == gotDocumentFormat && NULL != ipConfigurationName)
	{
		documentFormatType = GetFormatTypeFromString(documentFormatString);
		std::wstring configName(ipConfigurationName);
		SPAIopWString wConfigName(configName.c_str());
		bool updatedConfigName = AddOrUpdateConfigurationImportPolicy(wConfigName, documentFormatType, ioPolicy);
	}

	// Import Input Product Structure 
	SPAIopInputProductStructure inputPS;
	SPAIopPSImportResult inputPSResult;
	bool gotPS = ImportProductStructure(sourceDoc, ioPolicy, inputPS, inputPSResult);
	if (true == gotPS)
	{
		PrintPSRootInfo(inputPS);

		// Process the Input Product Structure for the given configuration.
		returnValue = ProcessInputProductStructure(inputPS, inputPSResult, ioPolicy, documentFormatType);

		// Delete _g_pProgressReporter here to stop spurious messages if the container has to be created.
		delete _g_pProgressReporter;
		_g_pProgressReporter = NULL;

		ClearImportedPartConfigurationCache();
	}
	else
	{
		returnValue = SPAIopSampleResult_Import_PS_Fail;
	}


	return returnValue;
}

//-----------------------------------------------------------------------------------------

int ProcessInputProductStructure(SPAIopInputProductStructure& iInputPS, SPAIopPSImportResult &iInputPSResult, const SPAIopPolicy& iPolicy, const SPAIopFormatType& iOptionFormat)
{
	int result = SPAIopSampleResult_Import_Ok;

	int nbInstances = iInputPS.GetRootInstancesCount();
	int i;
	for (i = 0; i < nbInstances; ++i)
	{
		SPAIopInputPSInstance inputRootInstance = iInputPS.GetRootInstance(i);

		PrintPSInstanceConfigInfo(inputRootInstance);

		SPAIopPSInstanceMiscData miscData = inputRootInstance.GetMiscData();
		SPAIopWString instanceConfigName = miscData.GetConfigurationName();

		bool isSuppressed(false);
		bool gotSuppressed = inputRootInstance.GetSuppressed(isSuppressed);
		if (true == gotSuppressed && true == isSuppressed)
		{
			PrintSkippingPSInstanceConfigInfo(inputRootInstance);

			// Skip over instances to references that are suppressed.
			continue;
		}

		SPAIopInputPSReference inputReference = inputRootInstance.GetReference();
		result = ProcessPSReference(inputReference, instanceConfigName, iPolicy, iOptionFormat);
	}

	return result;
}

//-----------------------------------------------------------------------------------------

int ProcessPSReference(SPAIopInputPSReference& iInputReference, const SPAIopWString& iInstanceConfigName, const SPAIopPolicy& iPolicy, const SPAIopFormatType& iOptionFormat)
{
	int result = SPAIopSampleResult_Import_Ok;

	if (iInputReference.IsPart())    // part node
	{
		if (false == PartConfigurationAlreadyImported(iInputReference, iInstanceConfigName))
		{
			PrintPSReferenceInfo(iInputReference);

			SPAIopPolicy localPolicy = iPolicy;
			bool updatedPolicy = AddOrUpdateConfigurationImportPolicy(iInstanceConfigName, iOptionFormat, localPolicy);

			// import the document
			ENTITY_LIST iopAcisEntities;
			result = ImportPart(iInputReference, localPolicy, iopAcisEntities);

			if (0 >= iopAcisEntities.count())
			{
				PrintMessage(L"No Entities\n");
			}
			else
			{
				CacheImportedPartConfiguration(iInputReference, iInstanceConfigName, iopAcisEntities);
			}
		}
	}
	else   // sub-assembly node
	{
		int nbInstances = iInputReference.GetInstancesCount();
		if (nbInstances > 0)
		{
			for (int i = 0; i < nbInstances; ++i)
			{
				SPAIopInputPSInstance iNodeInstance = iInputReference.GetInstance(i);

				PrintPSInstanceConfigInfo(iNodeInstance);

				SPAIopPSInstanceMiscData miscData = iNodeInstance.GetMiscData();
				SPAIopWString instanceConfigName = miscData.GetConfigurationName();

				bool isSuppressed(false);
				bool gotSuppressed = iNodeInstance.GetSuppressed(isSuppressed);
				if (true == gotSuppressed && true == isSuppressed)
				{
					PrintSkippingPSInstanceConfigInfo(iNodeInstance);

					// Skip over instances to references that are suppressed.
					continue;
				}

				SPAIopInputPSReference iNodeReference = iNodeInstance.GetReference();
				int res = ProcessPSReference(iNodeReference, instanceConfigName, iPolicy, iOptionFormat); // Recursively navigate nodes below this sub-assembly node
				if (0 != res)
					result = res;
			}
		}
	}

	return result;
}

//-----------------------------------------------------------------------------------------

bool ImportProductStructure(SPAIopDocument& ioSourceDocument, const SPAIopPolicy& iPolicy, SPAIopInputProductStructure& oInputPS, SPAIopPSImportResult &oInputPSResult)
{
	bool returnValue = true;

	// Import the Assembly tree as a Product Structure.  Note that 
	// importing a Product Structure does not import any brep data associated
	// with the model, which is done separately from part-associated reference nodes
	// in the Product Structure.  Use try/catch exception handling blocks to 
	// manage fatal errors.

	// Create a Product Strcuture Importer
	SPAIopProductStructureImporter psImporter;

	try
	{
		// Set Interop global progress report function if not already done
		psImporter.SetProgressCallBack((SPAIopProgressCallback)MyProgressCallBack);
		oInputPSResult = psImporter.ImportWithPolicy(iPolicy, ioSourceDocument, oInputPS);

		DumpMissingFilesInProductStructure(oInputPSResult);

		if (_g_pProgressReporter != NULL)
		{
			_g_pProgressReporter->SetNumParts(oInputPS.GetUniqueReferencesCount());
			_g_pProgressReporter->SetPsDone(true);
		}
	}
	catch (SPAIopAcisError& error)
	{
		DisplayError(error);
		returnValue = false;
	}
	catch (...)
	{
		std::wcout << "FAILURE: Unknown exception caught! " << std::endl;
		returnValue = false;
	}

	return returnValue;
}

//-----------------------------------------------------------------------------------------

int ImportPart(SPAIopInputPSReference& iInputReference, const SPAIopPolicy& iPolicy, ENTITY_LIST& oAcisEntities)
{
	// Import the brep data of a part into the given AcisEntities using the Import 
	// method of a SPAIopPartDocument object.  Use try/catch exception handling blocks to 
	// manage fatal errors.
	int result = SPAIopSampleResult_Import_Ok;
	SPAIopAcisPartImporter partImporter;

	unsigned int uniqueID = iInputReference.GetUniqueID();
	SPAIopWString storageNamePath = iInputReference.GetStorageName();
	SPAIopWString referenceName = iInputReference.GetReferenceName();

	SPAIopWString storageName;
	GetNameWithExtension(storageNamePath, storageName);

	const wchar_t* pStorageName = NULL;
	storageName.ToWChar(pStorageName);

	const wchar_t* pReferenceName = NULL;
	referenceName.ToWChar(pReferenceName);

	try
	{
		SPAIopPartDocument CGMInteropPartDocument = iInputReference.GetDocument();
		SPAIopPartImportResult importRes = partImporter.ImportWithPolicy(iPolicy, CGMInteropPartDocument, oAcisEntities);
	}
	catch (SPAIopAcisError& /*error*/)
	{
		// Do not display the error because a missing part is not a fatal error
		//DisplayError( error );
		PrintMessage(L"\n FAILURE : Part failed to import. UniqueID = %d, Storage Name = %ls, Reference Name = %ls \n", uniqueID, pStorageName, pReferenceName);
		result = SPAIopSampleResult_Import_Part_Fail;
		oAcisEntities.clear();
	}
	catch (...)
	{
		PrintMessage(L"\n FAILURE : Part failed to import. UniqueID = %d, Storage Name = %ls, Reference Name = %ls \n", uniqueID, pStorageName, pReferenceName);
		result = SPAIopSampleResult_Import_Part_Fail;
	}
	return result;
}

//-----------------------------------------------------------------------------------------

int test_IopAcisConfigurationImport(int argc, char* argv[])
{
	int result(1);
	SPAIopPerformanceStatistics printPerfoStatsObj;

	// Work-flow similar to Acis based app where Acis will be generally initialized before IOP is called.
	api_start_modeller(0);

	try
	{
		// IOP is called in its own scope.
		// This sample uses a guard class that initializes InterOp upon 
		// construction and terminates InterOp upon destruction.  All 
		// translations are done within the boundaries of a single 
		// initialization and shutdown.  InterOp may only be initialized 
		// and shutdown once per process.  Please do not use the guard 
		// classes multiple times per process.
		SPAIopAcisSystemInitGuard initGuard(SPATIAL_LICENSE);

		result = initGuard.GetInitStatus();

		if (SPA_UNLOCK_NO_MSG == result)
		{
			// Spatial products have been unlocked
			std::wcout << L"SUCCESS: SPAIopAcisSystem::Initialize. " << std::endl;
		}
		else if (SPA_UNLOCK_KEY_WARN_EXPIRE == result)
			std::wcout << L"SPAIopAcisSystem::Initialize returned SPA_UNLOCK_KEY_WARN_EXPIRE; your license key will expire soon!\n";
		else if (SPA_UNLOCK_REGISTRATION_WARN_EXPIRE == result)
			std::wcout << L"SPAIopAcisSystem::Initialize returned SPA_UNLOCK_REGISTRATION_WARN_EXPIRE; your registration will expire soon.\n";
		else
		{
			std::wcout << L"SPAIopAcisSystem::Initialize returned an unexpected value; Contact Spatial Customer Support or your Account Manager.\n";
			return 1;
		}
		result = 1;

		char* argFields[] = { "i", "o", "p", "config", "h", "log" };
		int size = sizeof(argFields) / sizeof(*argFields);
		SPAIopArgParser parser(size, argFields, argc, argv, &PrintHelp);

		if (true == parser.IsSuccess())
		{
			const wchar_t* pSourceFilePath = NULL;
			bool hasInputFileName = parser.GetWStringValueFor("i", pSourceFilePath);

			const wchar_t* pOutputFilePath = NULL;
			bool hasOutputFileName = parser.GetWStringValueFor("o", pOutputFilePath);

			const wchar_t* pPolicyFilePath = NULL;
			bool hasPolicyFileName = parser.GetWStringValueFor("p", pPolicyFilePath);

			const wchar_t* pLogFilePath = NULL;
			bool hasLogFileName = parser.GetWStringValueFor("log", pLogFilePath);

			const wchar_t* pConfigurationName = NULL;
			bool hasConfigurationName = parser.GetWStringValueFor("config", pConfigurationName);

			if (true == hasOutputFileName)
			{
				if (false == OpenFileForConsoleOutput(pOutputFilePath))
				{
					return 11;
				}
			}

			if (hasInputFileName && pSourceFilePath)
			{
				SPAIopPolicy policy;
				if (0 != pPolicyFilePath)
				{
					SPAIopWString policyFilePath(pPolicyFilePath);
					SPAIopPolicyFileStreamXML xmlPolicyStream;
					xmlPolicyStream.SetUnstreamFilePath(policyFilePath);

					policy.Deserialize(xmlPolicyStream);
				}

				SetOrUpdateLogFileInPolicy(pLogFilePath, policy);

				result = ImportConfiguration(pSourceFilePath, pConfigurationName, policy);
			}

			if (true == hasOutputFileName)
			{
				CloseFileForConsoleOutput();
			}
		}
	}
	catch (SPAIopAcisError& fatalErr)
	{
		DisplayError(fatalErr);
		result = 1;
	}
	catch (...)
	{
		PrintMessage(L"FAILURE: Unknown exception caught! ");
		result = 1;
	}

	api_stop_modeller();

	return result;
}

//-----------------------------------------------------------------------------------------

